
// ===============================================================================================================
// -*- C++ -*-
//
// Game.cpp - Game class. Encapsulates the game logic.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#include <Game.hpp>

// =========================================================
// Game Class Implementation
// =========================================================

Game::Game(void) : numPlayers(0), players(0), numLevels(0), levels(0), currentTime(0), lastTime(0)
{
}

bool Game::Initialize(void)
{
	// Call virtual initializers:

	if (InitRenderingSystem() && InitSoundSystem())
	{
		LoadGameData();
		return (true);
	}

	return (false);
}

void Game::UpdateAndRender(void)
{
	// Update the game timer:

	lastTime = currentTime;
	currentTime = GetElapsedSeconds();

	UpdateFrame(currentTime - lastTime);
	RenderFrame();
}

void Game::Quit(void)
{
	// Delete the players and levels:

	size_t i;

	for (i = 0; i < numPlayers; ++i)
	{
		delete players[i];
	}

	delete[] players;

	for (i = 0; i < numLevels; ++i)
	{
		levels[i]->Release();
	}

	delete[] levels;

	// Call virtual deinitializers:

	FreeGameData();
	TermSoundSystem();
	TermRenderingSystem();
}

Game::~Game(void)
{
}

// =========================================================
// FPS_GameDemo Class Implementation
// =========================================================

FPS_GameDemo::FPS_GameDemo(void)
: Game(), sndTrack(0), sndAmmoPickup(0), prcLoaded(0.0f), elapsedTime(0.0f), groundNmap(0), HellknightNmap(0), normalMapShader(0)
{
}

bool FPS_GameDemo::InitRenderingSystem(void)
{
	if (!Renderer::Initialize(1024, 768, (Renderer::INIT_DOUBLE_BUFFERED | Renderer::INIT_DEPTH_BUFFER /*| Renderer::INIT_FULLSCREEN*/), "DPD FPS-Game Framework"))
	{
		LOG_ERROR("Failed to initialize the game renderer!");
		return (false);
	}

	return (true);
}

bool FPS_GameDemo::InitSoundSystem(void)
{
	if (!FMOD::SoundSystem::Initialize(44100, 32, FSOUND_INIT_GLOBALFOCUS))
	{
		LOG_ERROR("Failed to initialize the sound system!");
		return (false);
	}

	return (true);
}

bool FPS_GameDemo::LoadGameData(void)
{
	groundNmap = Renderer::Instance()->Create2DTextureFromFile("Assets/Sprites/Ground_bump.tga");
	HellknightNmap = Renderer::Instance()->Create2DTextureFromFile("Assets/Textures/Monsters/Hellknight/Hellknight_bump.tga");

	const std::string shaderFiles[2] = { "Code/GLSL/NormalMap.vert", "Code/GLSL/NormalMap.frag" };
	normalMapShader = ShaderProgram::Create(shaderFiles, 2);

	// Init the game level as a random hight field:
	Texture * tex = Renderer::Instance()->Create2DTextureFromFile("Assets/Sprites/Ground.tga");
	
	numLevels = 1; // Just one level for now
	levels = new GameLevel*[numLevels];

	levels[0] = new ProceduralTerrain(50, 50, 50, 20, tex, "Dead City");
	tex->Release();

	// Call our level builder singleton:
	if (!LevelBuilder::Build(levels[0]))
	{
		LOG_ERROR("Failed to build the level!");
		return (false);
	}

	levels[0] = LevelBuilder::GetGameLevel();

	sndAmmoPickup = FMOD::SoundSystem::CreateSoundFromFile("Assets/Sounds/Weapons/Doublebarrel/ShellsPickup.ogg");
	sndTrack = FMOD::SoundSystem::CreateSoundFromFile("Assets/Sounds/DOOM 3 Theme.mp3");

	Vec3 randPos;
	int max_x, max_z;
	levels[0]->GetExtents(max_x, max_z);
	levels[0]->GetRandomPos(randPos, (max_x - 1) * max_x, (max_z - 1) * max_z);

	numPlayers = 1; // Single-player game
	players = new Player*[numPlayers];

	players[0] = new Player(8, randPos); // Start the player with 8 bullets at a random position.

	glEnable(GL_CULL_FACE);
	glFrontFace(GL_CW);

	ShowCursor(FALSE);

	// This must be called only after all models are loaded !!!
	DoomMD5Model::AllocSharedArrays();

	return (true);
}

void FPS_GameDemo::UpdateFrame(float elapsedTime)
{
	this->elapsedTime = elapsedTime;
}

void FPS_GameDemo::RenderFrame(void)
{
	Renderer * theRenderer = Renderer::Instance();

	theRenderer->BeginScene(Renderer::CLEAR_COLOR_BUFFER | Renderer::CLEAR_DEPTH_BUFFER);

	if (KeyDown(VK_MENU) && KeyDown(VK_RETURN)) // Go to fullscreen if ALT + ENTER is pressed
	{
		theRenderer->SwitchToFullScreen();
	}

	if (prcLoaded < 1.0f) // We are still loading, show the progress bar:
	{
		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

		char buf[128];
		sprintf(buf, "Loading ... %d", static_cast<int>(prcLoaded * 100.0f));

		static const unsigned int barColors[4] = { PackRGB(157, 85, 46), PackRGB(157, 85, 46), PackRGB(12, 60, 20), PackRGB(12, 155, 28) };
		theRenderer->DrawProgressBar(buf, barColors, 500, 50, prcLoaded);

		prcLoaded += 0.01f;
		Sleep(20);
	}
	else // In game:
	{
		glClearColor(0.58f, 0.47f, 0.36f, 0.0f);

		if (!sndTrack->IsPlaying())
		{
			sndTrack->Play();
		}

		players[0]->Update(levels[0], elapsedTime);

		glFrontFace(GL_CCW);
		theRenderer->DrawGameLevel(levels[0], groundNmap, normalMapShader);
		glFrontFace(GL_CW);

		unsigned int n = levels[0]->objects.size();

		while (n--) // Resolve the game objects:
		{
			switch (levels[0]->objects[n].type)
			{
			case GameLevel::FLAME_FIRE:
				{
					FireEffect::Enable();
					reinterpret_cast<const FireEffect *>(levels[0]->objects[n].objPointer)->Render();
					FireEffect::Disable();
					break;
				}
			case GameLevel::ENEMY_INSTANCE:
				{
					Enemy * enemy = reinterpret_cast<Enemy *>(levels[0]->objects[n].objPointer);
					enemy->Update(levels[0], players[0]->GetAngle(), elapsedTime);

					glFrontFace(GL_CCW);
					theRenderer->DrawDoomMD5Model(enemy->GetModel(), enemy->GetTransform()->m, HellknightNmap, normalMapShader);
					glFrontFace(GL_CW);

					const Vec3 eye(players[0]->GetCamera().GetEye());
					const Vec3 pos(enemy->GetPosition());

					const Vec3 diff(eye.x - pos.x, eye.y - pos.y, eye.z - pos.z);

					if ((diff.Length() < 80.0f) && players[0]->GetWeapon()->HasFired())
					{
						enemy->SetState(Enemy::AI_STATE_DEAD);
					}
					break;
				}
			case GameLevel::AMMO_BOXES:
				{
					AmmoBox * ammoBoxes = reinterpret_cast<AmmoBox *>(levels[0]->objects[n].objPointer);
					unsigned int n = NUM_AMMO_BOXES;

					const Vec3 eye(players[0]->GetCamera().GetEye());

					glFrontFace(GL_CCW);

					while (n--)
					{
						if (ammoBoxes[n].pickedUp == false)
						{
							const Vec3 & boxPos = ammoBoxes[n].Position();
							const Vec3 dist((eye.x - boxPos.x), (eye.y - boxPos.y), (eye.z - boxPos.z));

							if (dist.Length() <= 15.0f)
							{
								sndAmmoPickup->Play();
								players[0]->GetWeapon()->AddAmmo(ammoBoxes[n].NumOfBullets());
								ammoBoxes[n].pickedUp = true;
								continue;
							}

							ammoBoxes[n].Render();
						}
					}

					glFrontFace(GL_CW);
					break;
				}
			case GameLevel::SKY_BOX:
				{
					glFrontFace(GL_CCW);
					theRenderer->DrawSkyBox(reinterpret_cast<const SkyBox *>(levels[0]->objects[n].objPointer));
					glFrontFace(GL_CW);
					break;
				}
			}
		}

		players[0]->DisplayHUDAndWeapon();
		theRenderer->PrintString(10, 30, PackRGBA(50, 128, 50, 255), "FPS: %i", CalcFPS()); // Show our frame-rate.
	}

	theRenderer->EndScene(); // Flush the rendering pipeline.
}

void FPS_GameDemo::TermRenderingSystem(void)
{
	Renderer::Kill();
}

void FPS_GameDemo::TermSoundSystem(void)
{
	FMOD::SoundSystem::Shutdown();
}

void FPS_GameDemo::FreeGameData(void)
{
	if (sndTrack != 0)
	{
		sndTrack->Release();
	}

	if (sndAmmoPickup != 0)
	{
		sndAmmoPickup->Release();
	}

	if (groundNmap != 0)
	{
		groundNmap->Release();
	}

	if (HellknightNmap != 0)
	{
		HellknightNmap->Release();
	}

	if (normalMapShader != 0)
	{
		normalMapShader->Release();
	}

	DoomMD5Model::FreeSharedArrays();

	ShowCursor(TRUE);
}